#include <jni.h>
#include <string>
//#define DSP_FLOAT
#include "yzrutil/main/dsp/Delayer.h"
#include "yzrutil/main/dsp/Freeverb.h"
#include "yzrutil/main/dsp/Chorus.h"
#include "SimpleSynth/main/Mixer2.h"
#include "SimpleSynth/main/SynthUtil.h"
#include "yzrutil/main/io/FileInputStream.h"
#include "yzrutil/main/util/WAVWriter.h"
#include "SimpleSynth/main/tuning/EqualTemperament.h"
#include "SimpleSynth/main/instrument/SimpleMIDIInstrument.h"
#include "SimpleSynth/main/instrument/SF2FormatInstrument.h"
#include "SimpleSynth/main/instrument/DLSFormatInstrument.h"
#include <oboe/Oboe.h>
#include <android/log.h>
#include "yzrutil/main/io/ByteArrayInputStream.h"
#include "yzrutil/main/array/Array.hpp"
#include "yzrutil/main/lang/System.h"
#include "yzrutil/main/lang/String.h"
#include "yzrutil/main/dsp/Oscillator.h"

using namespace yzrilyzr_simplesynth;
using namespace yzrilyzr_util;
using namespace yzrilyzr_lang;
using namespace yzrilyzr_dsp;
using namespace oboe;
static constexpr int FLOAT_BUFFER_LEN = 256;
Mixer2 *mixer = new yzrilyzr_simplesynth::Mixer2(FLOAT_BUFFER_LEN);
std::shared_ptr<oboe::AudioStream> stream;
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "lib1", __VA_ARGS__)
int sdkInt;
std::optional<std::future<void>> fu;
static const std::string PLAY_MIDI = "PlayMIDI";

void mixBuffer(Mixer2 *mix) {
	mix->mix();
}

class FloatStreamCBK : public AudioStreamCallback {
public:
	DataCallbackResult
	onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
		if (fu.has_value()) {
			try {
				fu->get(); // 等待之前的混合任务完成
			} catch (const std::exception &e) {
				LOGI("Mix task failed: %s", e.what());
			}
			fu.reset(); // 重置 future
		}
		float *outputBuffer = static_cast<float *>(audioData);
		if (numFrames != mixer->getBufferSize()) {
			LOGI("%d numFrames", numFrames);
			return DataCallbackResult::Continue;
		}
		u_sample *ch0 = mixer->getOutput(0);
		u_sample *ch1 = mixer->getOutput(1);
		for (int i = 0, j = 0; j < numFrames;) {
			outputBuffer[i++] = ch0[j];
			outputBuffer[i++] = ch1[j++];
		}
		fu = std::async(std::launch::async, mixBuffer, mixer);
		return DataCallbackResult::Continue;
	}

	bool onError(AudioStream * /* audioStream */, Result /* error */) override {
		return false;
	}

	void onErrorBeforeClose(AudioStream * /* audioStream */, Result /* error */) override {}

	void onErrorAfterClose(AudioStream * /* audioStream */, Result /* error */) override {}
};

class I16StreamCBK : public AudioStreamCallback {
public:
	DataCallbackResult
	onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
		if (fu.has_value()) {
			try {
				fu->get(); // 等待之前的混合任务完成
			} catch (const std::exception &e) {
				LOGI("Mix task failed: %s", e.what());
			}
			fu.reset(); // 重置 future
		}
		int16_t *outputBuffer = static_cast<int16_t *>(audioData);
		if (numFrames != mixer->getBufferSize()) {
			LOGI("%d numFrames", numFrames);
			return DataCallbackResult::Continue;
		}
		u_sample *ch0 = mixer->getOutput(0);
		u_sample *ch1 = mixer->getOutput(1);
		for (int i = 0, j = 0; j < numFrames;) {
			outputBuffer[i++] = (int16_t) (ch0[j] * 32767.0f);
			outputBuffer[i++] = (int16_t) (ch1[j++] * 32767.0f);
		}
		fu = std::async(std::launch::async, mixBuffer, mixer);
		return DataCallbackResult::Continue;
	}

	bool onError(AudioStream * /* audioStream */, Result /* error */) override {
		return false;
	}

	void onErrorBeforeClose(AudioStream * /* audioStream */, Result /* error */) override {}

	void onErrorAfterClose(AudioStream * /* audioStream */, Result /* error */) override {}
};

AudioStreamCallback *cbk = nullptr;
uint32_t sampleRate = 44100;
extern "C" {
JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_postEvent(JNIEnv *env, jclass clazz, jint ty, jint data1,
													 jint data2) {
	SynthUtil::sendMIDIBytes(mixer, ty, data1, data2,PLAY_MIDI);
}
JNIEXPORT jint JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_init(JNIEnv *env, jclass clazz, jint sdkint) {
	AudioStreamBuilder builder;
	sdkInt = sdkint;
	mixer->setSynthMode(IMixer::MODE_THREAD_POOL, 8);
	mixer->setSampleRate(sampleRate);
	if (sdkInt > 23)cbk = new FloatStreamCBK();
	else cbk = new I16StreamCBK();
	builder.setSharingMode(SharingMode::Exclusive)->setPerformanceMode(
			PerformanceMode::PowerSaving)->setFormat(
			sdkInt > 23 ? AudioFormat::Float : AudioFormat::I16)->setFramesPerDataCallback(
			FLOAT_BUFFER_LEN)->setChannelCount(ChannelCount::Stereo)->setSampleRate(
			sampleRate)->setCallback(cbk);
	Result result = builder.openStream(stream);
	if (result == Result::OK && stream != nullptr) {
		//stream->setBuff
		stream->requestStart();
		return 0;
	} else {
		return -1;
	}
}
JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_reset(JNIEnv *env, jclass clazz) {
	mixer->reset();
}
JNIEXPORT jint JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_getCurrentProcessingNoteCount(JNIEnv *env,
																		 jclass clazz) {
	return mixer->getCurrentProcessingNoteCount();
}
JNIEXPORT jfloat JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_getProcessTime(JNIEnv *env, jclass clazz) {
	return mixer->getProcessTime();
}
JNIEXPORT jint JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_getPostedEventCount(JNIEnv *env, jclass clazz) {
	return mixer->getPostedEventCount();
}
JNIEXPORT jfloat JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_getProcessStandardTime(JNIEnv *env, jclass clazz) {
	return mixer->getProcessStandardTime();
}
JNIEXPORT jboolean JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_hasData(JNIEnv *env, jclass clazz) {
	return mixer->hasData();
}
JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setUseLimiter(JNIEnv *env, jclass clazz,
														 jboolean use_limiter) {
	mixer->setUseLimiter(use_limiter != 0);
}
JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_resetLimiter(JNIEnv *env, jclass clazz) {
	mixer->resetLimiter();
}
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setAFrequency(JNIEnv *env, jclass clazz,
														 jdouble a_freq) {
	if (auto t = dynamic_cast<EqualTemperament*>(mixer->getGlobalConfig().tuning)) {
		t->setBaseFreqByA(a_freq);
	}
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setDetune(JNIEnv *env, jclass clazz, jint i,
													 jfloat detune) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	auto *ev = new ChannelControl(i, MIDIFile::CC::EFFECT_DETUNE,
								  static_cast<uint8_t>(detune * 127));
	mixer->sendInstantEvent(ev);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setSustain(JNIEnv *env, jclass clazz, jint i,
													  jboolean sustain) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	auto *ev = new ChannelControl(i, MIDIFile::CC::SUSTAIN_SWITCH, sustain ? 127 : 0);
	mixer->sendInstantEvent(ev);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setDelayerRoomSize(JNIEnv *env, jclass clazz, jint i,
															  jint j, jdouble v) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	std::shared_ptr<IChannel> c = mixer->getMIDIChannel(PLAY_MIDI, i);
	auto &o = c->getReverb(j);
	o.roomSize = v;
	o.init(sampleRate);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setDelayerDamper(JNIEnv *env, jclass clazz, jint i,
															jint j, jdouble v) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	std::shared_ptr<IChannel> c = mixer->getMIDIChannel(PLAY_MIDI, i);
	auto &o = c->getReverb(j);
	o.damper = v;
	o.init(sampleRate);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setDelayerRatio(JNIEnv *env, jclass clazz, jint i,
														   jint j, jdouble dry_wet) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	std::shared_ptr<IChannel> c = mixer->getMIDIChannel(PLAY_MIDI, i);
	auto &o = c->getReverb(j);
	o.wetRatio = dry_wet;
	o.init(sampleRate);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setChorusDepth(JNIEnv *env, jclass clazz, jint i, jint j,
														  jdouble chorus_depth) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	std::shared_ptr<IChannel> c = mixer->getMIDIChannel(PLAY_MIDI, i);
	auto &o = c->getChorus(j);
	o.depthMs = (chorus_depth);
	o.init(sampleRate);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setChorusFeedback(JNIEnv *env, jclass clazz, jint i,
															 jint j, jdouble chorus_feedback) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	std::shared_ptr<IChannel> c = mixer->getMIDIChannel(PLAY_MIDI, i);
	auto &o = c->getChorus(j);
	o.feedback = (chorus_feedback);
	o.init(sampleRate);
}

extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setChorusRatio(JNIEnv *env, jclass clazz, jint i, jint j,
														  jdouble chorus_ratio) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	std::shared_ptr<IChannel> c = mixer->getMIDIChannel(PLAY_MIDI, i);
	auto &o = c->getChorus(j);
	o.wetRatio = (chorus_ratio);
	o.init(sampleRate);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_play(JNIEnv *env, jclass clazz) {
	stream->requestStart();
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_pause(JNIEnv *env, jclass clazz) {
	stream->requestPause();
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setSampleRate(JNIEnv *env, jclass clazz,
														 jint sample_rate) {
	sampleRate = sample_rate;
	stream->requestStop();
	stream->close();
	stream = nullptr;
	Java_yzrilyzr_simplesynth_app_NativePlayer_init(env, clazz, sdkInt);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_loadSoundbank(JNIEnv *env, jclass clazz, jstring path) {
	char *cPath = (char *) env->GetStringUTFChars(path, JNI_FALSE);
	yzrilyzr_io::FileInputStream isq(cPath);
	String sb(cPath);
	std::shared_ptr<InstrumentProvider> instr=nullptr;
	if(sb.endsWith(".sf2")){
		instr=std::make_shared<SF2FormatInstrument>(isq);
	}
	if(sb.endsWith(".dls")){
		instr=std::make_shared<DLSFormatInstrument>(isq);
	}
	if(instr!=nullptr)mixer->getGlobalConfig().setInstrumentProvider(instr);
	isq.close();
	env->ReleaseStringUTFChars(path, cPath);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_setChorusRate(JNIEnv *env, jclass clazz, jint i, jint j,
														 jdouble rate) {
	if (!mixer->hasMIDIChannel(PLAY_MIDI, i))return;
	std::shared_ptr<IChannel> c = mixer->getMIDIChannel(PLAY_MIDI, i);
	auto &o = c->getChorus(j);
	std::dynamic_pointer_cast<Oscillator>(o.osc)->freq = (rate);
	o.init(sampleRate);
}

std::shared_ptr<yzrilyzr_array::ByteArray> toByteArray(JNIEnv *env, jbyteArray javaData) {
	jsize length = env->GetArrayLength(javaData);
	jbyte *nativeData = env->GetByteArrayElements(javaData, nullptr);
	int8_t *arr1 = new int8_t[length];
	memcpy(arr1, nativeData, length);
	env->ReleaseByteArrayElements(javaData, nativeData, JNI_ABORT);
	return yzrilyzr_array::ByteArray::wrap(arr1, length);
}

std::shared_ptr<MixerSequence> parseSeq(std::shared_ptr<yzrilyzr_array::ByteArray> barr, int type) {
	std::shared_ptr<MixerSequence> seq = nullptr;
	yzrilyzr_io::ByteArrayInputStream bi(barr);
	if (type == 0)seq = SynthUtil::parseMIDI(bi);
	else if (type == 1)seq = SynthUtil::parseXM(bi);
	return seq;
}

extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_playBytes(JNIEnv *env, jclass clazz, jbyteArray javaData,
													 jint type) {
	std::shared_ptr<yzrilyzr_array::ByteArray> barr = toByteArray(env, javaData);
	std::shared_ptr<MixerSequence> seq = parseSeq(barr, type);
	if (seq != nullptr)seq->postToMixer(mixer, 0.1, PLAY_MIDI);
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_export(JNIEnv *env, jclass clazz, jbyteArray javaData,
												  jint type, jstring dstpath, jint sample_rate) {
	std::shared_ptr<yzrilyzr_array::ByteArray> barr = toByteArray(env, javaData);
	std::shared_ptr<MixerSequence> seq = parseSeq(barr, type);
	const char *c_str = env->GetStringUTFChars(dstpath, 0);
	yzrilyzr_util::WAVWriter raf((std::string(c_str)));
	env->ReleaseStringUTFChars(dstpath, c_str);
	size_t bufLength = 8192;
	Mixer2 mixer1(bufLength);
	mixer1.setSampleRate(sample_rate);
	raf.prepare(mixer1.getSampleRate(), 32, WAVWriter::FORMAT_FLOAT, 2);
	mixer1.setSynthMode(IMixer::MODE_THREAD_POOL, 10);
	mixer1.getGlobalConfig().set(mixer->getGlobalConfig());
	mixer1.setUseLimiter(false);
	seq->postToMixer(&mixer1, 0, "export");
	while (mixer1.hasData()) {
		mixer1.mix();
		std::cout << mixer1.getPostedEventCount() << std::endl;
		for (uint32_t sample = 0, j = 0; sample < mixer1.getBufferSize(); sample++) {
			for (int ch = 0, chc = mixer1.getOutputChannelCount(); ch < chc; ch++) {
				float f1 = (float) mixer1.getOutput(ch)[sample];
				raf.writeFloat(f1);
			}
			raf.nextFrame();
		}
	}
	raf.end();
	std::cout << "Write" << std::endl;
}
extern "C" JNIEXPORT void JNICALL
Java_yzrilyzr_simplesynth_app_NativePlayer_defaultInstrument(JNIEnv *env, jclass clazz) {
	std::shared_ptr<InstrumentProvider> inst = std::make_shared<SimpleMIDIInstrument>();
	mixer->getGlobalConfig().setInstrumentProvider(inst);
}